/*
 moxl - Modular XML Library
 Copyright (c) 2007, Malcolm Sparks

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package moxl.xpath.parser.types;

import moxl.xpath.XPathContext;
import moxl.xpath.parser.Operand;
import moxl.xpath.parser.AbstractOperator;

public class Expression implements NonTerminal, Operand
{
	private Operand firstOperand, secondOperand;
	private AbstractOperator operator, nextOperator;
	private boolean negateFirstOperand, negateSecondOperand, negateThirdOperand;

	public void addOperand(Operand operand)
	{
		if (this.firstOperand == null)
		{
			this.firstOperand = operand;
		}
		else if (this.secondOperand == null)
		{
			this.secondOperand = operand;
		}
		else
		{
			if (operator == null || nextOperator == null)
			{
				throw new IllegalStateException("Cannot add operand to this expression without first adding an operator.");
			}
			if (operator.compareTo(nextOperator) == 1)
			{
				Expression newExpression = new Expression();
				if (negateFirstOperand)
					newExpression.addOperator(AbstractOperator.MINUS);
				newExpression.addOperand(firstOperand);
				newExpression.addOperator(operator);
				if (negateSecondOperand)
					newExpression.addOperator(AbstractOperator.MINUS);
				newExpression.addOperand(secondOperand);
				operator = nextOperator;
				nextOperator = null;
				firstOperand = newExpression;
				secondOperand = operand;
			}
			else
			{
				Expression newExpression = new Expression();
				if (negateSecondOperand)
				{
					newExpression.addOperator(AbstractOperator.MINUS);
				}
				newExpression.addOperand(secondOperand);
				newExpression.addOperator(nextOperator);
				if (negateThirdOperand)
				{
					newExpression.addOperator(AbstractOperator.MINUS);
				}
				newExpression.addOperand(operand);
				secondOperand = newExpression;
				nextOperator = null;
			}
		}
	}

	public void addOperator(AbstractOperator operator)
	{
		if (this.firstOperand != null && this.secondOperand == null && this.operator == null && this.nextOperator == null)
		{
			this.operator = operator;
		}
		else if (this.firstOperand != null && this.secondOperand != null && this.operator != null && this.nextOperator == null)
		{
			this.nextOperator = operator;
		}
		else if (operator.equals(AbstractOperator.MINUS))
		{
			if (this.firstOperand == null)
			{
				negateFirstOperand = true;
			}
			else if (secondOperand == null)
			{
				negateSecondOperand = true;
			}
			else
			{
				negateThirdOperand = true;
			}
		}
		else
		{
			throw new IllegalStateException("Expression is not expecting an operator here.");
		}
	}

	public Operand getFirstOperand()
	{
		return firstOperand;
	}

	public Operand getSecondOperand()
	{
		return secondOperand;
	}

	public AbstractOperator getOperator()
	{
		return operator;
	}

	public boolean isPrimary()
	{
		return firstOperand != null && operator == null && secondOperand == null;
	}

	public AbstractXPathValue evaluate(XPathContext ctx) throws XPathEvaluationException
	{
		if (operator != null)
		{
			if (operator.equals(AbstractOperator.AND_OPERATOR))
			{
				AbstractXPathValue val1 = firstOperand.evaluate(ctx);
				XPathBooleanValue bval1 = convertToBoolean(val1);
				if (bval1.getValueAsBoolean() == false)
				{
					return XPathBooleanValue.FALSE;
				}
				else
				{
					AbstractXPathValue val2 = secondOperand.evaluate(ctx);
					XPathBooleanValue bval2 = convertToBoolean(val2);
					return operator.evaluate(ctx, bval1, bval2);
				}

			}
			else if (operator.equals(AbstractOperator.OR_OPERATOR))
			{
				AbstractXPathValue val1 = firstOperand.evaluate(ctx);
				XPathBooleanValue bval1 = convertToBoolean(val1);
				if (bval1.getValueAsBoolean() == true)
				{
					return XPathBooleanValue.TRUE;
				}
				else
				{
					AbstractXPathValue val2 = secondOperand.evaluate(ctx);
					XPathBooleanValue bval2 = convertToBoolean(val2);
					return operator.evaluate(ctx, bval1, bval2);
				}

				/*
				 * } else if (operator.equals(Operator.EQUALS)) { // TODO: Or other comparison operators, just do = for now XPathValue val1 =
				 * firstOperand.evaluate(ctx); XPathValue val2 = secondOperand.evaluate(ctx);
				 * 
				 * 
				 * 
				 * if (val1 instanceof XPathNodeSetValue && val2 instanceof XPathStringValue) { if
				 * (((XPathNodeSetValue)val1).containsNodeWithSpecificStringValue(val2.getValueAsString())) { return XPathBooleanValue.TRUE; } else { return
				 * XPathBooleanValue.FALSE; } } else { throw new UnsupportedOperationException("Unsupported types used for = operator."); }
				 */

			}
			else
			{
				AbstractXPathValue val1 = firstOperand.evaluate(ctx);
				AbstractXPathValue val2 = secondOperand.evaluate(ctx);
				if (negateFirstOperand)
					val1.negate();
				if (negateSecondOperand)
					val2.negate();
				return operator.evaluate(ctx, val1, val2);
			}
		}
		else
		{
			AbstractXPathValue result = firstOperand.evaluate(ctx);
			if (negateFirstOperand)
				result.negate();
			return result;
		}
	}

	/**
	 * This method is a place-holder until we have a proper implementation of boolean(), as per the spec.
	 */
	private XPathBooleanValue convertToBoolean(AbstractXPathValue val)
	{
		boolean result;
		if (val instanceof XPathBooleanValue)
		{
			return (XPathBooleanValue) val;
		}
		else if (val instanceof XPathNumberValue)
		{
			result = val.getValueAsDouble() != 0d && val.getValueAsDouble() != Double.NaN;
		}
		else if (val instanceof XPathStringValue)
		{
			result = val.getValueAsString().length() > 0;
		}
		else
		{
			// TODO: Node sets and other types.
			throw new UnsupportedOperationException("Don't know how to convert " + val.getTypeName() + " to a boolean value.");
		}
		return result ? XPathBooleanValue.TRUE : XPathBooleanValue.FALSE;
	}

}
